/*
 * Dictionary.cpp
 *
 *  Created on: 2017年6月20日
 *      Author: heqian
 */

#include "Dictionary.h"
#include <unistd.h>
#include <fstream>
#include "EnumPos.h"

namespace mmseg {

DicFile::DicFile(const string& path) :
		mNext(nullptr) {
	mFile = path;
}

DicFile::~DicFile() {
	if (mNext) {
		delete mNext;
		mNext = nullptr;
	}
}

void DicFile::addFile(const string& path) {
	DicFile* temp = mNext;
	mNext = new DicFile(path);
	mNext->mNext = temp;
}

DicFile* DicFile::getNext() {
	return mNext;
}

const string& DicFile::getFile() const {
	return mFile;
}

Dictionary* Dictionary::mInstance = nullptr;

Dictionary::Dictionary() :
		mNode(nullptr), mDefaultDic(nullptr) {
	muSymobl = (UTF_8*) malloc(sizeof(UTF_8) * 48);
	UTF_INIT(muSymobl[0], "《");
	UTF_INIT(muSymobl[1], "》");
	UTF_INIT(muSymobl[2], "【");
	UTF_INIT(muSymobl[3], "】");
	UTF_INIT(muSymobl[4], "。");
	UTF_INIT(muSymobl[5], "？");
	UTF_INIT(muSymobl[6], "、");
	UTF_INIT(muSymobl[7], "“");
	UTF_INIT(muSymobl[8], "”");
	UTF_INIT(muSymobl[9], "‘");
	UTF_INIT(muSymobl[10], "’");
	UTF_INIT(muSymobl[11], "：");
	UTF_INIT(muSymobl[12], "；");
	UTF_INIT(muSymobl[13], "·");
	UTF_INIT(muSymobl[14], "「");
	UTF_INIT(muSymobl[15], "`");
	UTF_INIT(muSymobl[16], "～");
	UTF_INIT(muSymobl[17], "！");
	UTF_INIT(muSymobl[18], "￥");
	UTF_INIT(muSymobl[19], "…");
	UTF_INIT(muSymobl[20], "×");
	UTF_INIT(muSymobl[21], "（");
	UTF_INIT(muSymobl[22], "）");
	UTF_INIT(muSymobl[23], "—");
	UTF_INIT(muSymobl[24], "＜");
	UTF_INIT(muSymobl[25], "＞");
	UTF_INIT(muSymobl[26], "．");
	UTF_INIT(muSymobl[27], "／");
	UTF_INIT(muSymobl[28], "＂");
	UTF_INIT(muSymobl[29], "＇");
	UTF_INIT(muSymobl[30], "｛");
	UTF_INIT(muSymobl[31], "｝");
	UTF_INIT(muSymobl[32], "［");
	UTF_INIT(muSymobl[33], "］");
	UTF_INIT(muSymobl[34], "｜");
	UTF_INIT(muSymobl[35], "＼");
	UTF_INIT(muSymobl[36], "｀");
	UTF_INIT(muSymobl[37], "＠");
	UTF_INIT(muSymobl[38], "＃");
	UTF_INIT(muSymobl[39], "％");
	UTF_INIT(muSymobl[40], "＾");
	UTF_INIT(muSymobl[41], "＆");
	UTF_INIT(muSymobl[42], "＊");
	UTF_INIT(muSymobl[43], "－");
	UTF_INIT(muSymobl[44], "＿");
	UTF_INIT(muSymobl[45], "＝");
	UTF_INIT(muSymobl[46], "＋");
	UTF_INIT(muSymobl[47], "，");
}

Dictionary::~Dictionary() {
	free(muSymobl);
	CharNode* next = mNode;
	CharNode* temp;
	while (next) {
		temp = next->getNext();
		delete next;
		next = temp;
	}
	mNode = nullptr;

	if (mDefaultDic) {
		delete mDefaultDic;
		mDefaultDic = nullptr;
	}

}

void Dictionary::init(DicFile* files) {
	CharNode* node = nullptr;
	CharNode* temp;
	while (files) {
		readDicFile(&node, files->getFile());
		files = files->getNext();
	}
	temp = mNode;
	mNode = node;

	while (temp) {
		node = temp->getNext();
		delete temp;
		temp = node;
	}
}

string Dictionary::getExtDirectory() {
	char path[1024];
	char* str = path;
	char* temp = nullptr;
	int rslt = readlink("/proc/self/exe", path, 1024 - 1);
	if (rslt < 0 || (rslt >= 1024 - 1)) {
		return NULL;
	}

	while (*str) {
		if ('/' == *str)
			temp = str;
		str++;
	}

	if (temp) {
		*(temp + 1) = 0;
		*(temp + 2) = 0;
	}
	return path;
}

DicFile* Dictionary::defaultDictionary() {
	string str;
	if (!mDefaultDic) {
		str = getExtDirectory();

		mDefaultDic = new DicFile(str + "word.dic");

	}
	return mDefaultDic;
}

void Dictionary::readDicFile(CharNode** node, const string& path) {
	char *line;
	size_t len = 1024;
	ssize_t read;

	FILE* file = fopen(path.c_str(), "r");
	if (file) {
		line = (char*) malloc(1024); // 如果分配太小的话，会出错
		if (isUtf_8(file)) {
			while ((read = getline(&line, &len, file)) != EOF) {
				addDicWord(node, line, read);
			}
		}
		free(line);
		fclose(file);
	}
}

void Dictionary::save(const char* savePath) {
	if (!mNode)
		return;

	string path;
	if (savePath)
		path = savePath;
	else
		path = defaultDictionary()->getFile();

	ofstream ofile;
	ofile.open(path);
	if (ofile.is_open()) {
		outWord(mNode, "", [&](string word) {
			ofile << word << endl;
		}, true);
		ofile.close();
	}
}

void Dictionary::outWord(OutWord out) {
	if (mNode)
		outWord(mNode, "", out, true);
}

const string Dictionary::outKey(UTF_8 key) {
	char* str = (char*) &key;
	char* end = str + UTF_SIZE(key);
	string ret;
	int size = 0;
	char temp;
	UTF_8 ckey = 0;

	if ('\\' == *str) {
		str++;
		while (str < end) {
			ret.append("\\");

			temp = *str;
			size = 1;
			if (temp & 0x80) {
				temp <<= 1;
				do {
					temp <<= 1;
					++size;
				} while (temp & 0x80);
			}
			memcpy(&ckey, str, size);
			ret.append((char*) &ckey);
			str++;
		}
	} else {
		ret.append(str);
	}

	return ret;
}

void Dictionary::outWord(CharNode* node, const char* str, OutWord out, bool isNext) {
	string text = str;
	string pos;
	string other;
	UTF_8 key = node->getKey();
	string word;

	text.append(outKey(key));
	if (node->isIsWord()) {
		word.append(text);
		pos = posToString(node->getPos());
		if (pos.size()) {
			word.append(" ").append(pos);
		}
		other = node->getOther();
		if (other.size()) {
			word.append(" ").append(other);
		}
		out(word);
	}
	if (*(node->getNode()))
		outWord(*(node->getNode()), text.c_str(), out, true);

	while (isNext && (node = node->getNext()))
		outWord(node, str, out, false);
}

void Dictionary::addDicWord(CharNode** node, char* str, int len) {
	char* end = str + len;
	char* text[3] = { str, nullptr, nullptr };
	int i = 1;
	char temp = 0;

	while (str < end) {
		if ('"' == *str) {
			temp = temp ? 0 : *str;
		}

		if (!temp && (' ' == *str || '\r' == *str || '\n' == *str)) {
			*str = 0;
			str++;
			if (str < end && i < 3) {
				text[i] = str;
				i++;
				continue;
			}
			break;
		}

		str++;
	}

	if ((len = strlen(text[0])))
		addWord(node, text[0], len, (i > 1) ? stringToPos(text[1]) : un, (i > 2) ? text[2] : "");
}

bool Dictionary::isUtf_8(FILE* file) {
	bool ret = true;

	return ret;
}

void Dictionary::addWord(const string& word, EnumPos pos, const string& other) {
	addWord(&mNode, word.c_str(), word.size(), pos, other);
}

void Dictionary::addWord(CharNode** node, const char* str, int num, EnumPos pos, const string& other) {
	int len;
	int i = 0;
	CharNode* item = nullptr;
	UTF_8 key;

	while (i < num) {
		len = num - i;
		key = getChar(str + i, &len);
		i += len;

		if (*node) {
			item = (*node)->find(key, true);
			node = &item;
		} else {
			*node = item = new CharNode();
			item->setKey(key);
		}
		node = item->getNode();

	}
	if (item) {
		item->setPos(pos);
		item->setOther(other);
		item->setIsWord(true);
	}
}

UTF_8 Dictionary::getChar(const char* str, int* len) {
	UTF_8 key = 0;
	char* pKey = (char*) &key;
	int i = 0;
	int size = 0;
	char temp;
	bool is = false;

	while (i < *len) {
		temp = str[i];
		if (temp & 0x80) {
			if (0 != size)
				break;

			size = 1;
			temp <<= 1;
			do {
				temp <<= 1;
				++size;
			} while (temp & 0x80);
			memcpy(&key, str + i, size);
			i = size;
			break;
		} else {
			if ('\\' == temp) {
				if (6 <= size)
					break;
				if (0 == size) {
					pKey[size] = '\\';
					size++;
				}
				is = true;
			} else if (is) {
				pKey[size] = temp;
				size++;
				is = false;
			} else {
				if (0 == size) {
					pKey[size] = temp;
					size++;
					i++;
				}
				break;
			}
			i++;
		}
	}
	*len = i;
	UTF_SIZE(key) = size;
	return key;
}

Dictionary & Dictionary::getInstance() {
	if (!mInstance) {
		mInstance = new Dictionary();
	}

	return *mInstance;
}

void Dictionary::freeDictionary() {
	if (mInstance) {
		delete this;
		mInstance = nullptr;
	}
}

CharNode * Dictionary::getCharNode() {
	return mNode;
}

bool Dictionary::isNumber(UTF_8 key) {
	return 0x100000000000030 <= key && 0x100000000000039 >= key;
}

bool Dictionary::isLower(UTF_8 key) {
	return 0x100000000000061 <= key && 0x10000000000007A >= key;
}

bool Dictionary::isUpper(UTF_8 key) {
	return 0x100000000000041 <= key && 0x10000000000005A >= key;
}

bool Dictionary::isLowerSymbol(UTF_8 key) {
	return (0x100000000000001 <= key && 0x10000000000002F >= key) || (0x10000000000003A <= key && 0x100000000000040 >= key)
			|| (0x10000000000005B <= key && 0x100000000000060 >= key) || (0x10000000000007B <= key && 0x10000000000007F >= key);
}

bool Dictionary::isUpperSymbol(UTF_8 key) {
	for (int i = 0; i < 48; i++) {
		if (key == muSymobl[i])
			return true;
	}
	return false;
}

} /* namespace mmseg */
